#include "check.h"

namespace ieda_contest {

CheckOverflow::CheckOverflow(ContestDataManager* data_manager)
{
  /**
   * GCell在每个轴上是均匀的
   * instance的GCell坐标：instance的bounding_box中点的Gcell坐标
   */
  _data_manager = data_manager;
  makeLayerInfo();
  makeGCellInfo();
  // makeInstanceList();
  // makeNetList();
}

void CheckOverflow::makeLayerInfo()
{
  std::map<std::string, int>& layer_name_to_idx_map = _data_manager->get_database()->get_layer_name_to_idx_map();
  std::map<int, std::string>& layer_idx_to_name_map = _data_manager->get_database()->get_layer_idx_to_name_map();
  std::map<int, idb::IdbLayerDirection>& layer_idx_to_direction_map = _data_manager->get_database()->get_layer_idx_to_direction_map();

  int layer_idx = 0;
  for (idb::IdbLayer* idb_layer : _data_manager->get_idb_layout()->get_layers()->get_layers()) {
    if (idb_layer->is_routing()) {
      idb::IdbLayerRouting* idb_routing_layer = dynamic_cast<idb::IdbLayerRouting*>(idb_layer);
      layer_name_to_idx_map[idb_routing_layer->get_name()] = layer_idx;
      layer_idx_to_name_map[layer_idx] = idb_routing_layer->get_name();
      layer_idx_to_direction_map[layer_idx] = idb_routing_layer->get_direction();
      layer_idx++;
    }
  }
}

void CheckOverflow::makeGCellInfo()
{
  std::map<std::string, int>& layer_name_to_idx_map = _data_manager->get_database()->get_layer_name_to_idx_map();

  std::set<int> single_gcell_x_span_set;
  std::set<int> single_gcell_y_span_set;
  for (idb::IdbGCellGrid* idb_gcell_grid : _data_manager->get_idb_layout()->get_gcell_grid_list()->get_gcell_grid_list()) {
    if (idb_gcell_grid->get_direction() == idb::IdbTrackDirection::kDirectionX) {
      single_gcell_x_span_set.insert(idb_gcell_grid->get_space());
    } else if (idb_gcell_grid->get_direction() == idb::IdbTrackDirection::kDirectionY) {
      single_gcell_y_span_set.insert(idb_gcell_grid->get_space());
    } else {
      std::cout << "[error] The direction of gcell grid is invaild!" << std::endl;
      exit(1);
    }
  }
  if (single_gcell_x_span_set.size() == 0) {
    std::cout << "[error] The x_gcell_grid is empty!" << std::endl;
    exit(1);
  }
  if (single_gcell_y_span_set.size() == 0) {
    std::cout << "[error] The y_gcell_grid is empty!" << std::endl;
    exit(1);
  }
  if (single_gcell_x_span_set.size() > 1) {
    std::cout << "[error] The x_gcell_grid is uneven!" << std::endl;
    exit(1);
  }
  if (single_gcell_y_span_set.size() > 1) {
    std::cout << "[error] The y_gcell_grid is uneven!" << std::endl;
    exit(1);
  }
  int single_gcell_x_span = *single_gcell_x_span_set.begin();
  int single_gcell_y_span = *single_gcell_y_span_set.begin();
  /**
   * single_gcell_x_span
   * single_gcell_y_span
   * single_gcell_area
   */
  _data_manager->get_database()->set_single_gcell_x_span(single_gcell_x_span);
  _data_manager->get_database()->set_single_gcell_y_span(single_gcell_y_span);
  _data_manager->get_database()->set_single_gcell_area(single_gcell_x_span * single_gcell_y_span);
  // layer_gcell_supply_map
  std::map<int, int>& layer_gcell_supply_map = _data_manager->get_database()->get_layer_gcell_supply_map();
  for (idb::IdbLayer* idb_layer : _data_manager->get_idb_layout()->get_layers()->get_layers()) {
    if (idb_layer->is_routing()) {
      idb::IdbLayerRouting* idb_routing_layer = dynamic_cast<idb::IdbLayerRouting*>(idb_layer);

      int prefer_pitch = 0;
      for (idb::IdbTrackGrid* idb_track_grid : idb_routing_layer->get_track_grid_list()) {
        idb::IdbTrack* idb_track = idb_track_grid->get_track();

        if (idb_routing_layer->get_direction() == idb::IdbLayerDirection::kHorizontal
            && idb_track->get_direction() == idb::IdbTrackDirection::kDirectionY) {
          prefer_pitch = idb_track->get_pitch();
        } else if (idb_routing_layer->get_direction() == idb::IdbLayerDirection::kVertical
                   && idb_track->get_direction() == idb::IdbTrackDirection::kDirectionX) {
          prefer_pitch = idb_track->get_pitch();
        }
      }
      if (idb_routing_layer->get_direction() == idb::IdbLayerDirection::kHorizontal) {
        layer_gcell_supply_map[layer_name_to_idx_map[idb_routing_layer->get_name()]] = (single_gcell_y_span / prefer_pitch);
      } else if (idb_routing_layer->get_direction() == idb::IdbLayerDirection::kVertical) {
        layer_gcell_supply_map[layer_name_to_idx_map[idb_routing_layer->get_name()]] = (single_gcell_x_span / prefer_pitch);
      }
    }
  }
}

ContestCoord CheckOverflow::getGCellCoord(const ContestCoord& coord)
{
  int single_gcell_x_span = _data_manager->get_database()->get_single_gcell_x_span();
  int single_gcell_y_span = _data_manager->get_database()->get_single_gcell_y_span();

  ContestCoord gcell_coord;
  gcell_coord.set_x((coord.get_x() > 0 ? coord.get_x() - 1 : coord.get_x()) / single_gcell_x_span);
  gcell_coord.set_y((coord.get_y() > 0 ? coord.get_y() - 1 : coord.get_y()) / single_gcell_y_span);
  gcell_coord.set_layer_idx(coord.get_layer_idx());
  return gcell_coord;
}

void CheckOverflow::makeNetList()
{
  std::map<std::string, int>& layer_name_to_idx_map = _data_manager->get_database()->get_layer_name_to_idx_map();
  int single_gcell_x_span = _data_manager->get_database()->get_single_gcell_x_span();
  int single_gcell_y_span = _data_manager->get_database()->get_single_gcell_y_span();

  for (ContestNet& contest_net : _data_manager->get_database()->get_net_list()) {
    idb::IdbNet* idb_net = _data_manager->get_idb_design()->get_net_list()->find_net(contest_net.get_net_name());
    // pin_list
    std::vector<ContestPin>& pin_list = contest_net.get_pin_list();
    pin_list.clear();
    for (idb::IdbPin* idb_pin : idb_net->get_instance_pin_list()->get_pin_list()) {
      ContestCoord coord(idb_pin->get_instance()->get_bounding_box()->get_middle_point_x(),
                         idb_pin->get_instance()->get_bounding_box()->get_middle_point_y(), 0);

      ContestPin contest_pin;
      contest_pin.set_real_coord(coord);
      contest_pin.set_grid_coord(getGCellCoord(coord));
      contest_pin.get_contained_instance_list().push_back(idb_pin->get_instance()->get_name());
      contest_pin.get_contained_pin_list().push_back(std::string(idb_pin->get_instance()->get_name() + ":" + idb_pin->get_pin_name()));
      pin_list.push_back(contest_pin);
    }
    if (idb_net->get_io_pin() != nullptr) {
      idb::IdbPin* io_pin = idb_net->get_io_pin();
      ContestCoord coord(io_pin->get_bounding_box()->get_middle_point_x(), io_pin->get_bounding_box()->get_middle_point_y(), 0);

      ContestPin contest_pin;
      contest_pin.set_real_coord(coord);
      contest_pin.set_grid_coord(getGCellCoord(coord));
      contest_pin.get_contained_instance_list().push_back("io_pin");
      contest_pin.get_contained_pin_list().push_back(io_pin->get_pin_name());
      pin_list.push_back(contest_pin);
    }
    // routing_segment_list
    std::map<std::pair<int, int>, std::set<int>> planar_layer_map;
    for (ContestGuide& contest_guide : contest_net.get_guide_list()) {
      int layer_idx = layer_name_to_idx_map[contest_guide.get_layer_name()];
      ContestCoord real_lb_coord(contest_guide.get_lb_x() + (single_gcell_x_span / 2), contest_guide.get_lb_y() + (single_gcell_y_span / 2),
                                 layer_idx);
      ContestCoord real_rt_coord(contest_guide.get_rt_x() - (single_gcell_x_span / 2), contest_guide.get_rt_y() - (single_gcell_y_span / 2),
                                 layer_idx);
      ContestCoord grid_lb_coord = getGCellCoord(real_lb_coord);
      ContestCoord grid_rt_coord = getGCellCoord(real_rt_coord);

      std::pair<int, int> lb_planar_coord(grid_lb_coord.get_x(), grid_lb_coord.get_y());
      std::pair<int, int> rt_planar_coord(grid_rt_coord.get_x(), grid_rt_coord.get_y());

      planar_layer_map[lb_planar_coord].insert(layer_idx);
      planar_layer_map[rt_planar_coord].insert(layer_idx);
    }
    // 删除layer只有一层的
    std::set<std::pair<int, int>> remove_key_set;
    for (auto& [planar_coord, layer_set] : planar_layer_map) {
      if (layer_set.size() == 1) {
        remove_key_set.insert(planar_coord);
      }
    }
    for (const auto& key : remove_key_set) {
      planar_layer_map.erase(key);
    }
    /////////////////////////////////////////////
    std::vector<ContestSegment>& routing_segment_list = contest_net.get_routing_segment_list();
    routing_segment_list.clear();
    // 对这些添加proximal线段
    for (auto& [planar_coord, layer_set] : planar_layer_map) {
      int low_layer_idx = *layer_set.begin();
      auto layer_set_end = layer_set.end();
      layer_set_end--;
      int high_layer_idx = *layer_set_end;

      routing_segment_list.emplace_back(ContestCoord(planar_coord.first, planar_coord.second, low_layer_idx),
                                        ContestCoord(planar_coord.first, planar_coord.second, high_layer_idx));
    }
    // 对这些添加平面线段
    for (ContestGuide& contest_guide : contest_net.get_guide_list()) {
      int layer_idx = layer_name_to_idx_map[contest_guide.get_layer_name()];
      ContestCoord real_lb_coord(contest_guide.get_lb_x() + (single_gcell_x_span / 2), contest_guide.get_lb_y() + (single_gcell_y_span / 2),
                                 layer_idx);
      ContestCoord real_rt_coord(contest_guide.get_rt_x() - (single_gcell_x_span / 2), contest_guide.get_rt_y() - (single_gcell_y_span / 2),
                                 layer_idx);
      ContestCoord grid_lb_coord = getGCellCoord(real_lb_coord);
      ContestCoord grid_rt_coord = getGCellCoord(real_rt_coord);
      if (grid_lb_coord == grid_rt_coord) {
        // 删除点线段
        continue;
      }
      routing_segment_list.emplace_back(grid_lb_coord, grid_rt_coord);
    }
  }
}

bool CheckOverflow::overflowCheckPassed(ContestDataManager* data_manager)
{
  if (data_manager) {
    _data_manager = data_manager;
  }
  makeNetList();
  int overflow_gcell_count = 0;
  bool check_result = true;
  int max_overflow = 0;
  std::map<int, int>& layer_gcell_supply_map = _data_manager->get_database()->get_layer_gcell_supply_map();
  std::map<int, int> layer_to_overflow_map;

  std::map<ContestCoord, int, CmpContestCoord> coord_demand_map;
  for (ContestNet& contest_net : _data_manager->get_database()->get_net_list()) {
    std::set<ContestCoord, CmpContestCoord> coord_set;
    for (ContestSegment& routing_segment : contest_net.get_routing_segment_list()) {
      int lb_x = routing_segment.get_first().get_x();
      int lb_y = routing_segment.get_first().get_y();
      int lb_layer_idx = routing_segment.get_first().get_layer_idx();

      int rt_x = routing_segment.get_second().get_x();
      int rt_y = routing_segment.get_second().get_y();
      int rt_layer_idx = routing_segment.get_second().get_layer_idx();

      if (lb_layer_idx != rt_layer_idx) {
        continue;
      }

      for (int x = lb_x; x <= rt_x; x++) {
        for (int y = lb_y; y <= rt_y; y++) {
          for (int layer_idx = lb_layer_idx; layer_idx <= rt_layer_idx; layer_idx++) {
            if ((lb_x != rt_x && (x == lb_x || x == rt_x)) || (lb_y != rt_y && (y == lb_y || y == rt_y))) {
              continue;
            }
            coord_set.insert(ContestCoord(x, y, layer_idx));
          }
        }
      }
    }
    for (const ContestCoord& coord : coord_set) {
      if (coord_demand_map[coord] == layer_gcell_supply_map[coord.get_layer_idx()]) {
        std::cout << "Overflow GCell: (" << coord.get_x() << "," << coord.get_y() << "," << coord.get_layer_idx() << ")" << std::endl;
        check_result = false;
        overflow_gcell_count++;
        layer_to_overflow_map[coord.get_layer_idx()]++;
        _overflow_gcell.insert(coord);
        _overflow_net_id.insert(contest_net.get_id());
      }

      _gcell_nets[coord].push_back(contest_net.get_id());
      _net_gcells[contest_net.get_id()].insert(coord);

      coord_demand_map[coord] += 1;
      auto overflow = coord_demand_map[coord] - layer_gcell_supply_map[coord.get_layer_idx()];
      max_overflow = std::max(max_overflow, overflow);
      _gcell_used[coord] = coord_demand_map[coord];
    }
  }

  std::cout << "Max Overflow: " << max_overflow << std::endl;
  // output layer to overflow map
  std::cout << "Layer to Overflow Map: " << std::endl;
  for (auto& item : layer_to_overflow_map) {
    std::cout << item.first << ": " << item.second << std::endl;
  }

  return check_result;
}

}  // namespace ieda_contest